#!/usr/bin/env python
from math import sin, pi, hypot

import pygame
from pygame import Rect

from lib.utils import worldDistance, elementDistance
from lib.dynamics import Mass, Constraint, Point, Line, Curve
from lib.sodalike import ActuatorGroup
from main import main

key = main.key
mouse = main.mouse

class Container(object):
    """A widget container.
    Trees of these recursively relay events to lowly widgets.
    """
    events = ('hover', 'wheel', 'click', 'drag', 'release', 'draw')
    def __init__(self, root, x, y, w, h):
        self.updateList = root.updateList
        self.theme = root.theme
        
        self.rect = Rect(x, y, w, h)
        
        self.widgets = []
        
        self._active = None
        self._dragged = set()
        
        self.eventUsers = {'hover': set(),
                           'wheel': set(),
                           'click': set(),
                           'drag': set(),
                           'release': set(),
                           'draw': [], #Item order is important
                           }
        
        root.register(self)
    
    @property
    def hits(self):
        if mouse.x != mouse.xPrev or mouse.y != mouse.yPrev:
            self._hits = set(w for w in self.widgets \
                             if w.rect.collidepoint(mouse.x, mouse.y))
        return self._hits
    
    def register(self, widget):
        self.widgets.append(widget)
        for event in widget.events:
            self.registerForEvent(widget, event)
    
    def registerForEvent(self, widget, event):
        if event == 'update':
            self.updateList.append(widget)
        elif event == 'draw':
            self.eventUsers[event].append(widget)
        else:
            self.eventUsers[event].add(widget)
    
    def unregister(self, widget):
        self.widgets.remove(widget)
        for event in widget.events:
            self.unregisterForEvent(widget, event)
    
    def unregisterForEvent(self, widget, event):
        if event == 'update':
            self.updateList.remove(widget)
        else:
            self.eventUsers[event].remove(widget)
    
    def on_wheel(self, wheel):
        for widget in self.hits:
            if widget in self.eventUsers['wheel']:
                widget.on_wheel(wheel)
    
    def on_click(self, x, y, button):
        for widget in self.hits:
            if widget in self.eventUsers['click']:
                if widget in self.eventUsers['release']:
                    self._active = widget
                widget.on_click(x, y, button)
            if widget in self.eventUsers['drag']:
                self._dragged.add(widget)
    
    def on_drag(self, x, y, dx, dy, button):
        for widget in self._dragged:
            widget.on_drag(x, y, dx, dy, button)
    
    def on_release(self, x, y, button):
        if self._active:
            self._active.on_release(x, y, button)
            self._active = None
        self._dragged.clear()
        for widget in self.hits:
            if widget in self.eventUsers['release']:
                widget.on_release(x, y, button)
    
    def on_hover(self, x, y):
        for widget in self.hits:
            if widget in self.eventUsers['hover']:
                widget.on_hover(x, y)
    
    def on_draw(self, surface):
        for widget in self.eventUsers['draw']:
            widget.on_draw(surface)

class Button:
    events = ('click', 'release', 'draw')
    def __init__(self, root, x, y, w, h, icon=None):
        self.rect = Rect(x, y, w, h)
        
        self.pressed = False
        
        self.normalImage = root.theme.render_button(w, h)
        self.pressedImage = root.theme.render_button_pressed(w, h)
        
        if icon:
            self.normalImage = self.normalImage.copy()
            self.normalImage.blit(icon[0], (0, 0))
            self.pressedImage = self.pressedImage.copy()
            self.pressedImage.blit(icon[0], (0, 0))
        
        self.image = self.normalImage
        
        root.register(self)
    
    def bindFunction(self, function, arguments=[]):
        self.function = function
        self.arguments = arguments
        self.on_click = self.on_click_bound
    
    def on_click(self, x, y, button):
        self.pressed = True
        self.image = self.pressedImage
    
    def on_click_bound(self, x, y, button):
        self.pressed = True
        self.image = self.pressedImage
        self.function(*self.arguments)
    
    def on_release(self, x, y, button):
        self.pressed = False
        self.image = self.normalImage
    
    def on_draw(self, surface):
        surface.blit(self.image, self.rect)

class Slider(object):
    events = ('drag', 'click', 'release', 'draw')
    def __init__(self, root, x, y, w, h, icon=None):
        self.rect = Rect(x, y, w, h)
        self.image = root.theme.render_tab(w, h)
        
        self.bRect = Rect(x, y, h, h)
        self.bImage = root.theme.render_button(h, h)
        self.bPressedImage = root.theme.render_button_pressed(h, h)
        
        if icon:
            self.bImage = self.bImage.copy()
            self.bImage.blit(icon[0], (0, 0))
            self.bPressedImage = self.bPressedImage.copy()
            self.bPressedImage.blit(icon[0], (0, 0))
        
        self.corsa = float(self.rect.right - self.rect.left - self.bRect.width)
        self.pressed = False
        
        root.register(self)
    
    @property
    def value(self):
        return (self.bRect.left - self.rect.left) / self.corsa
    
    @value.setter
    def value(self, val):
        self.bRect.left = self.rect.left + val * self.corsa
    
    def on_click(self, x, y, button):
        if self.bRect.collidepoint(x, y):
            self.pressed = True
    
    def on_drag(self, x, y, dx, dy, button):
        if self.pressed:
            self.bRect.centerx += dx
            self.bRect.clamp_ip(self.rect)
    
    def on_release(self, x, y, button):
        self.pressed = False
    
    def on_draw(self, surface):
        surface.blit(self.image, self.rect)
        if self.pressed:
            surface.blit(self.bPressedImage, self.bRect)
        else:
            surface.blit(self.bImage, self.bRect)

class Knob:
    events = ('drag', 'draw')
    def __init__(self, root, x, y, w, h, icon=None):
        self.rect = Rect(x, y, w, h)
        
        self.value = 0.0
        
        self.image = root.theme.render_knob(w, h)
        
        root.register(self)
    
    def on_drag(self, x, y, dx, dy, button):
        pass
    
    def on_draw(self, surface):
        surface.blit(self.image, self.rect)

class Selection:
    MODE_ITEM = 0
    MODE_RECT = 1
    def __init__(self):
        self.rect = Rect(0, 0, 0, 0)
        
        self.mode = self.MODE_ITEM
        
        self.items = []
    
    def clear(self):
        self.rect.size = 0, 0
        self.items = []
    
    def add(self, item):
        if not item in self.items:
            self.items.append(item)

class WorldWidget:
    events = ('wheel', 'click', 'update', 'draw')
    def __init__(self, root, world, x, y, w, h):
        self.world = world
        
        self.rect = Rect(x, y, w, h)
        
        self.tool = self.notool
        
        self.selection = Selection()
        
        self.action = 0 #1 = selecting, 2 = moving
        
        self.sortedElements = [] #Sorted by their distance to the cursor
        
        #OBJECT CREATION PARAMETRES
        self.weight = 1.0
        self.length = 60.0
        self.stiffness = 0.1
        self.catch = 15.0
        
        root.register(self)
    
    def on_wheel(self, delta):
        if self.tool == self.select:
            for item in self.selection.items:
                if item.__class__ == Constraint:
                    #Constrain constraints
                    item.length += delta
                    if item.length < 1.0:
                        item.length = 1.0
                    item.squareLength = item.length ** 2
                    for group in main.wave.actuatorGroups:
                        if item in group.constraints:
                            item.originalLength += delta
                            if item.originalLength < 1.0:
                                item.originalLength = 1.0 
                if item.__class__ == Mass:
                    #Massify masses
                    item.weight += delta * 0.2
                    if item.weight < 1.0: item.weight = 1.0
                    for k in self.world.constraints:
                        if item == k.mass1 or item == k.mass2:
                            k.refresh()
        elif self.tool == self.constraint:
            self.length += delta
        elif self.tool == self.quickMake:
            self.length += delta
    
    def on_click(self, x, y, button):
        pass
    
    def on_update(self):
        if self.rect.collidepoint(mouse.x, mouse.y):
            self.tool()
        
        self.sortedElements = worldDistance(self.world.elements, mouse.x,
                                                                 mouse.y)
    
    def on_draw(self, surface):
        bg = 255, 255, 255
        fg = 0, 0, 0
        c1 = 255, 127, 0
        c2 = 0, 0, 255
        pygame.draw.rect(surface, bg, self.rect)
        
        for mass in self.world.masses:
            self.drawMass(surface, fg, mass)
        
        for constraint in self.world.constraints:
            self.drawConstraint(surface, fg, constraint)
        
        for line in self.world.lines:
            self.drawLine(surface, fg, line)
        
        for curve in self.world.curves:
            self.drawCurve(surface, fg, curve)
        
        #Selection highlights
        for item in self.selection.items:
            if item.__class__ == Mass:
                self.drawMass(surface, c1, item)
            if item.__class__ == Constraint:
                self.drawConstraint(surface, c1, item)
        
        #Context sensitive graphics
        if self.tool == self.select:
            if len(main.world.elements) > 0:
                #Selection Rectangle
                if len(self.selection.items) > 1 or (mouse.pressed[0] and self.action == 1):
                    pygame.draw.rect(surface, fg, self.selection.rect, 1)
                
                nearestElement = self.sortedElements[0]
                if nearestElement.__class__ == Mass:
                    self.drawMass(surface, c1, nearestElement)
                if nearestElement.__class__ == Constraint:
                    self.drawConstraint(surface, c1, nearestElement)
        
        elif self.tool == self.quickMake:
            if len(self.world.masses) == 0:
                pygame.draw.circle(surface, c1, (mouse.x, mouse.y), 3, 1)
            else:
                nearestMass = self.getNearest(Mass)
                if len(self.selection.items) > 0:
                    if (nearestMass.x - mouse.x) ** 2 + (nearestMass.y - mouse.y) ** 2 > self.catch ** 2:
                        #Preview of a constraint connecting selection[0] and a new mass
                        pygame.draw.aaline(surface, c1,
                                           (self.selection.items[0].x,
                                            self.selection.items[0].y), (mouse.x, mouse.y))
                        pygame.draw.circle(surface, c1, (mouse.x, mouse.y), 3, 1)
                    else:
                        #Preview of a constraint connecting selection[0] and an existing mass
                        pygame.draw.aaline(surface, c1,
                                           (self.selection.items[0].x,
                                            self.selection.items[0].y),
                                           (nearestMass.x, nearestMass.y))
                else:
                    if hypot(nearestMass.x - mouse.x, nearestMass.y - mouse.y) > self.catch:
                        #Preview of a new mass
                        pygame.draw.circle(surface, c1, (mouse.x, mouse.y), 3, 1)
                    else:
                        #Preview of a mass close enough for selection
                        pygame.draw.circle(surface, c1, (int(nearestMass.x),
                                                    int(nearestMass.y)), 3, 1)
        
        elif self.tool == self.actuatorGroup:
            selectedGroup = main.interface.actuatorGroupPanel.selectedGroup
            if selectedGroup:
                for constraint in selectedGroup.constraints:
                    self.drawActuator(surface, c2, constraint)
    
    def drawMass(self, surface, color, mass):
        pygame.draw.circle(surface, color, (int(mass.x), int(mass.y)),
                           int(3 * mass.weight), 1)
    
    def drawConstraint(self, surface, color, constraint):
        m1, m2 = constraint.mass1, constraint.mass2
        pygame.draw.aaline(surface, color, (m1.x, m1.y), (m2.x, m2.y))
        constraint.drawn = True
    
    def drawLine(self, surface, color, line):
        pygame.draw.aaline(surface, color, (line.p1.x, line.p1.y),
                                           (line.p2.x, line.p2.y))
    
    def drawCurve(self, surface, color, curve, segments=16):
        pos0 = curve.p1.x, curve.p1.y
        for i in range(segments):
            pos1 = curve.getPoint((i + 1) / float(segments))
            pygame.draw.aaline(surface, color, pos0, pos1)
            pos0 = pos1
    
    def drawActuator(self, surface, color, constraint):
        a = constraint.mass1
        b = constraint.mass2
        pygame.draw.aaline(surface, color,
                       (a.x - 2 + (b.x - a.x) * 0.2, a.y + (b.y - a.y) * 0.2),
                       (a.x - 2 + (b.x - a.x) * 0.8, a.y + (b.y - a.y) * 0.8))
    
    #TOOLS
    #TOOLS
    #TOOLS
    #BEHOLD
    def notool(self):
        """Not delivering on expectations since 2008"""
        pass
    
    def select(self):
        """Selects all items from a container which are in the
        selection rectangle's area. If no rectangle gesture is made,
        it selects the nearest item.
        
        """
        rect = self.selection.rect
        
        if mouse.checkClicked(0):
            if len(self.world.masses) > 0:
                nearestElement = n = self.sortedElements[0]
                nearestDist = elementDistance(n, mouse.x, mouse.y)
                nearEnough = nearestDist < self.catch ** 2
                
                if rect.collidepoint(mouse.x, mouse.y):
                    self.action = 2 #Moving
                else:
                    if nearEnough:
                        self.selection.add(nearestElement)
                        self.action = 2 #Moving
                    else:
                        if not key.checkPressed(pygame.K_LSHIFT):
                            self.selection.clear()
                        rect.topleft = mouse.x, mouse.y
                        rect.size = 0, 0
                        self.action = 1 #Selecting
        elif mouse.pressed[0]:
            if self.action == 1:
                #Update selectionRect
                rect.size = mouse.x - rect.left, mouse.y - rect.top
            elif self.action == 2:
                #Move the selected masses
                if len(self.selection.items) == 1:
                    self.selection.items[0].x = mouse.x
                    self.selection.items[0].y = mouse.y
                else:
                    rect.left += mouse.x - mouse.xPrev
                    rect.top += mouse.y - mouse.yPrev
                    for item in self.selection.items:
                        if item.__class__.__name__ == "Mass":
                            item.x += mouse.x - mouse.xPrev
                            item.y += mouse.y - mouse.yPrev
                            item.xPrev = item.x
                            item.yPrev = item.y
        elif mouse.pressedPrev[0]:
            if self.action == 1:
                rect.normalize()
                if rect.width > 4 and rect.height > 4: #Not really dragging
                   #Select the masses inside selectionRect
                    for mass in self.world.masses:
                        if rect.collidepoint(mass.x, mass.y):
                            self.selection.add(mass)
                else: #But if the mouse stood still
                    #Select the nearest element
                    if len(self.sortedElements) > 0:
                        nearestItem = self.sortedElements[0]
                        self.selection.add(nearestItem)
            
            if len(self.selection.items) == 1:
                if self.selection.items[0].__class__ == Mass:
                    self.selection.clear()
        
        """DESELECTION"""
        if mouse.checkClicked(2):
            self.selection.clear()
    
    def quickMake(self):
        """Makes structures of masses and constraints"""
        if mouse.checkClicked(0) or mouse.checkClicked(1):
            #Select a close particle or make one, until there are enough
            if self.world.masses:
                nMass = self.getNearest(Mass)
                nDistance = (mouse.x - nMass.x)**2 + (mouse.y - nMass.y)**2
                if nDistance < self.catch ** 2:
                    self.selection.add(nMass)
                else:
                    self.selection.items.append(self.makeMass(mouse.x, mouse.y))
            else: #No masses at all yet
                self.selection.items.append(self.makeMass(mouse.x, mouse.y))
        
        if len(self.selection.items) == 2:
            #If enough particles are selected, link them and select the second one
            if mouse.pressedPrev[0]:
                self.makeConstraint(self.selection.items[0], self.selection.items[1], self.length)
                self.selection.items.pop(0)
            if mouse.pressedPrev[1]:
                self.makeConstraint(self.selection.items[0], self.selection.items[1])
                self.selection.items.pop(0)
        
        if mouse.checkClicked(2):
            if len(self.selection.items) == 0:
                if len(self.world.masses) > 0:
                    nearestElement = self.sortedElements[0]
                    dist = elementDistance(nearestElement, mouse.x, mouse.y)
                    if dist < self.catch ** 2:
                        if nearestElement.__class__ == Mass:
                            self.removeMass(nearestElement)
                        elif nearestElement.__class__ == Constraint:
                            self.removeConstraint(nearestElement)
            else:
                self.selection.clear()
    
    def actuatorGroup(self):
        """Adds constraints to the currently selected actuator group"""
        if len(main.wave.actuatorGroups) > 0:
            if len(self.world.constraints) > 0:
                if mouse.checkClicked(0):
                    nearestConstraint = self.getNearest(Constraint)
                    self.actuatorGroupPanel.addConstraint(nearestConstraint)
                if mouse.checkClicked(2):
                    nearestConstraint = self.getNearest(Constraint)
                    self.actuatorGroupPanel.removeConstraint(nearestConstraint)
    
    #SUBTOOLS
    def getNearest(self, itemType):
        """Returns the element of the chosen type nearest to the mouse cursor"""
        for item in self.sortedElements:
            if item.__class__ == itemType:
                return item
    
    def makeMass(self, x, y):
        """Makes a mass at mouse position with standard arguments"""
        mass = Mass(x, y, self.weight)
        self.world.masses.append(mass)
        return mass
    
    def makeStaticMass(self, x, y):
        """Makes a static mass at mouse position with standard arguments"""
        mass = StaticMass(x, y, self.weight)
        self.world.masses.append(mass)
        return mass
    
    def removeMass(self, mass):
        #Queue attached constraints for deletion
        constraintsTBD = set()
        for constraint in self.world.constraints:
            if mass == constraint.mass1 or mass == constraint.mass2:
                constraintsTBD.add(constraint)
        
        #Remove the mass from wherever it may be
        if mass in self.world.masses:
            self.world.masses.remove(mass)
        if mass in main.fluid.masses:
            main.fluid.masses.remove(mass)
        
        #Remove the attached constraints
        for constraint in constraintsTBD:
            self.removeConstraint(constraint)
    
    def makeConstraint(self, mass1, mass2, length=None):
        c = Constraint(mass1, mass2, length, self.stiffness)
        self.world.constraints.append(c)
        return c
    
    def removeConstraint(self, constraint):
        """Removes every possible reference to a constraint.
        No kittens are harmed in the process."""
        self.selection.clear()
        self.world.constraints.remove(constraint)
        for group in main.wave.actuatorGroups:
            if constraint in group.constraints:
                group.removeConstraint(constraint)
        #harmKittens()
    """ ^ See? It's commented out. Safe, surely."""

class DynamicTab(Container):
    """A rectangular widget container, made for being resized in real time.
    
    Note that its w attribute is not its width, it's its west side's x-coord.
    """
    def __init__(self, root, x, y, w, h):
        Container.__init__(self, root, x, y, w, h)
        self._n_prev = y
        self._w_prev = x
        self._s_prev = y + h
        self._e_prev = x + w
        self._n_update = False
        self._w_update = False
        self._s_update = False
        self._e_update = False
        self._nw_update = False
        self._ne_update = False
        self._sw_update = False
        self._se_update = False
        self._redraw = True
    
    def get_n(self): return self.rect.top
    def get_w(self): return self.rect.left
    def get_s(self): return self.rect.bottom
    def get_e(self): return self.rect.right
    
    def set_n(self, value):
        self._n_update = True
        self._nw_update = True
        self._ne_update = True
        self._n_prev = self.rect.top
        self.rect.height = self.s - self.n
        self.rect.top = value
    
    def set_w(self, value):
        self._w_update = True
        self._nw_update = True
        self._sw_update = True
        self._w_prev = self.rect.left
        self.rect.width = self.e - self.w
        self.rect.left = value
    
    def set_s(self, value):
        self._s_update = True
        self._sw_update = True
        self._se_update = True
        self._s_prev = self.rect.bottom
        self.rect.height = self.s - self.n
        self.rect.bottom = value
    
    def set_e(self, value):
        self._e_update = True
        self._ne_update = True
        self._se_update = True
        self._e_prev = self.rect.right
        self.rect.width = self.e - self.w
        self.rect.right = value
    
    n = property(get_n, set_n)
    w = property(get_w, set_w)
    s = property(get_s, set_s)
    e = property(get_e, set_e)
    
class ThemedDynamicTab(DynamicTab):
    def __init__(self, root, x, y, w, h):
        DynamicTab.__init__(self, root, x, y, w, h)
        
        self.theme = root.theme

class VStaff(object):
    """Staves exist because they are single objects to which a behavior
    can be attached.
    """
    def __init__(self, x, left, right):
        """@param x: horizontal position of the staff
        @param left: the Tab to the left of the staff
        @param right: the Tab to the right of the staff
        """
        self.left = left
        self.right = right
        self.x = x
    
    def get_x(self):
        return self.left.e
    
    def set_x(self, value):
        self.left.e = value
        self.right.w = value
    
    x = property(get_x, set_x)

class HStaff:
    """Staves exist because they are single objects to which a behavior
    can be attached.
    """
    def __init__(self, y, above, under):
        """@param y: horizontal position of the staff
        @param above: the Tab above the staff
        @param under: the Tab under the staff
        """
        self._y = y
        self.above = above
        self.under = under
    
    def get_y(self):
        return self._y
    
    def set_y(self, value):
        self._y = value
        self.above.s = value
        self.under.n = value
    
    y = property(get_y, set_y)

class BarIcon:
    def __init__(self):
        pass

class DynamicBar(object):
    def __init__(self, root, x, y):
        """@param x, y: center of the bar"""
        self.x = x
        self.y = y
        self.rect = Rect(x, y, 0, 0)
        
        self.tabs = []   #t0  t1  t2...
        self.staves = [] #  s1  s2...
        
        self.selectedTab = None

class HDynamicBar(DynamicBar):
    """An horizontal bar which moves tabs around"""
    events = ('click', )
    def __init__(self, root, x, y):
        DynamicBar.__init__(self, root, x, y)
        
        root.register(self)
    
    def add(self, tab):
        """Adds tab to the bar"""
        self.rect.h = max(self.rect.h, tab.rect.h)
        i = len(self.tabs) #Index of the tab which is to be added
        self.tabs.append(tab)
        if i > 0:
            #Put a staff between the old rightmost tab and the new one
            self.staves.append(VStaff(0.0, self.tabs[i-1], self.tabs[i]))
        else:
            self.selectedTab = tab
        self.updateStaves()
    
    def select(self, tab):
        """Selects a tab"""
        if tab != self.selectedTab:
            self.deselect(self.selectedTab)
            tab._selected = True
            self.selectedTab = tab
            self.updateStaves()
    
    def deselect(self, tab):
        tab._selected = False
    
    def updateStaves(self):
        """Sets the separators in motion once the widths are determined"""
        w = 0
        for tab in self.tabs:
            w += tab.rect.w
        self.rect.w = w
        self.rect.center = self.x
        for s in self.staves:
            s.x = s.left.w + s.left.rect.w
    
    def on_click(self, x, y, button):
        for tab in self.tabs:
            if tab.rect.collidepoint(x, y):
                self.select(tab)

class SettingsPanel(ThemedDynamicTab):
    events = Container.events + ('update', )
    def __init__(self, root, x, y, w, h):
        ThemedDynamicTab.__init__(self, root, x, y, w, h)
        
        self.gSlider = Slider(self, x, y, w, 24, root.theme.gravityLabel)
        self.fSlider = Slider(self, x, y+25, w, 24, root.theme.frictionLabel)
        self.kSlider = Slider(self, x, y+50, w, 24, root.theme.springStrenghtLabel)
        
        self.gSlider.value = 0.0
        self.fSlider.value = 0.8
        self.kSlider.value = 0.8
        
        root.register(self)
        
    def on_update(self):
        main.world.gravity = sin(self.gSlider.value * pi / 2.0)
        main.world.conservation = 1 - sin(self.fSlider.value * pi / 2.0)
        main.interface.worldWidget.stiffness = self.kSlider.value

class ActuatorController(ThemedDynamicTab):
    def __init__(self, root, group):
        ThemedDynamicTab.__init__(self, root, root.rect.x, 0, root.rect.w, 16)
        
        self.group = group
        
        t = main.interface.theme
        self.font = f = t.font
        self.varValText = f.render(str(self.group.variation), True, (0,0,0))
        self.offValText = f.render(str(self.group.offset), True, (0,0,0))
        self.spdValText = f.render(str(self.group.speed), True, (0,0,0))
        
        u = 16
        plusImage = t.plusImage
        minusImage = t.minusImage
        
        self.delButton = Button(self, 0, 0, 12, 12, minusImage)
        
        self.varUpButton = Button(self, u, 0, 12, 12, plusImage)
        self.varDownButton = Button(self, u*4, 0, 12, 12, minusImage)
        
        self.offUpButton = Button(self, u*5, 0, 12, 12, plusImage)
        self.offDownButton = Button(self, u*8, 0, 12, 12, minusImage)
        
        self.spdUpButton = Button(self, u*9, 0, 12, 12, plusImage)
        self.spdDownButton = Button(self, u*12, 0, 12, 12, minusImage)
        
        self.delButton.bindFunction(root.removeController, (self, ))
        self.varUpButton.bindFunction(self.setVariation, (0.05, ))
        self.varDownButton.bindFunction(self.setVariation, (-0.05, ))
        self.offUpButton.bindFunction(self.setOffset, (0.05, ))
        self.offDownButton.bindFunction(self.setOffset, (-0.05, ))
        self.spdUpButton.bindFunction(self.setSpeed, (0.05, ))
        self.spdDownButton.bindFunction(self.setSpeed, (-0.05, ))
    
    def setVariation(self, delta):
        self.group.variation += delta
        self.varValText = self.font.render(str(self.group.variation), True, (0,0,0))
    
    def setOffset(self, delta):
        self.group.offset += delta
        self.offValText = self.font.render(str(self.group.offset), True, (0,0,0))
    
    def setSpeed(self, delta):
        self.group.speed += delta
        self.spdValText = self.font.render(str(self.group.speed), True, (0,0,0))
    
    def on_draw(self, surface):
        ThemedDynamicTab.on_draw(self, surface)
        
        x, y = self.rect.topleft
        u = 16
        surface.blit(self.varValText, (x+u*2, y))
        surface.blit(self.offValText, (x+u*6, y))
        surface.blit(self.spdValText, (x+u*10, y))

class ActuatorPanel(ThemedDynamicTab):
    def __init__(self, root, x, y, w, h):
        ThemedDynamicTab.__init__(self, root, x, y, w, h)
        
        self.controllers = []
        
        plusImage = root.theme.plusImage
        self.addGroupButton = Button(self, x, y+16, 24, 12, plusImage)
        self.addGroupButton.bindFunction(self.addController)
        
        f = root.theme.font
        self.varText = f.render("Variation", True, (0,0,0))
        self.offText = f.render("Offset", True, (0,0,0))
        self.spdText = f.render("Speed", True, (0,0,0))
        
        root.register(self)
    
    def addController(self):
        group = main.wave.addActuatorGroup()
        controller = ActuatorController(self, group)
        controller.rect.y = self.n + 16 * len(main.wave.actuatorGroups)
        self.controllers.append(controller)
        
        
        self.addGroupButton.rect.y = self.n + 16 + 16 * len(main.wave.actuatorGroups)
    
    def removeController(self, controller):
        self.controllers.remove(controller)
        main.wave.removeActuatorGroup(controller.group)
        self.unregister(controller)
        
        self.addGroupButton.rect.y = self.n + 16 + 16 * len(main.wave.actuatorGroups)
    
    def on_draw(self, surface):
        ThemedDynamicTab.on_draw(self, surface)
        
        x, y = self.rect.topleft
        u = 16
        surface.blit(self.varText, (x+u*2, y))
        surface.blit(self.offText, (x+u*6, y))
        surface.blit(self.spdText, (x+u*10, y))

class Context:
    def __init__(self, icon, action=None):
        self.rect = Rect(0, 0, 0, 0)
        self.iconImage = icon
        self.action = action

class ContextBar:
    events = ('click', 'draw')
    def __init__(self, root, x, y, w, h):
        self.root = root
        self.rect = Rect(x, y, w, h)
        
        t = root.theme
        self.font = t.font
        
        self.leftSize = t.barLeftImage[1].width
        self.rightSize = t.barRightImage[1].width
        self.elementSize = t.barInsetImage[1].width
        
        self.leftImage = t.barLeftImage[0]
        self.centerImage = t.barCenterImage[0]
        self.rightImage = t.barRightImage[0]
        self.insetImage = t.barInsetImage[0]
        
        #CONTEXTS
        self.selectedElement = None
        self.selectedElementNumber = 0
        
        self.elements = []
        
        self.addElement(icon=root.theme.saveWorldLabel[0],
                        action=self.enableSettingsMode)
        self.addElement(icon=root.theme.selectLabel[0],
                        action=self.enableSelectMode)
        self.addElement(icon=root.theme.structLabel[0],
                        action=self.enableQuickMakeMode)
        self.refresh()
        
        root.register(self)
    
    def enableSettingsMode(self):
        self.root.worldWidget.selection.clear()
        self.root.worldWidget.tool = self.root.worldWidget.notool
    
    def enableSelectMode(self):
        self.root.worldWidget.selection.clear()
        self.root.worldWidget.tool = self.root.worldWidget.select
    
    def enableQuickMakeMode(self):
        self.root.worldWidget.selection.clear()
        self.root.worldWidget.tool = self.root.worldWidget.quickMake
    
    def addElement(self, icon, action=None):
        element = Context(icon, action)
        self.elements.append(element)
        return element
    
    def refresh(self):
        """Call this after you're done adding buttons to the bar"""
        #Positioning
        for element in self.elements:
            element.rect.height = self.rect.height
            element.rect.centery = self.rect.centery
        
        self.selectedElement = self.elements[0]
        self.selectedElementNumber = 0
        
        left = 0
        for i, element in enumerate(self.elements):
            if i == self.selectedElementNumber:
                right = self.rect.width - (len(self.elements) - i - 1) * self.elementSize
            else:
                right = left + self.elementSize
            element.rect.width = right - left
            element.rect.left = self.rect.left + left
            
            left = right #And move onto the next element
    
    def on_click(self, x, y, button):
        for i, element in enumerate(self.elements):
            if element.rect.collidepoint(x, y):
                self.selectedElement = element
                self.selectedElement.action()
                self.selectedElementNumber = i
                break
        
        left = 0
        for i, element in enumerate(self.elements):
            if i == self.selectedElementNumber:
                right = self.rect.width - (len(self.elements) - i - 1) * self.elementSize
            else:
                right = left + self.elementSize
            element.rect.width = right - left
            element.rect.left = self.rect.left + left
            
            left = right #And move onto the next element
    
    def on_draw(self, surface):
        pygame.draw.rect(surface, (255,255,255), self.rect)
        for element in self.elements:
            left = element.rect.left
            if element == self.selectedElement:
                right = element.rect.right - self.rightSize
                surface.blit(self.leftImage, (left, self.rect.top))
                surface.blit(self.rightImage, (right, self.rect.top))
                for i in range(left + self.leftSize, right):
                    surface.blit(self.centerImage, (i, self.rect.top))
            else:
                surface.blit(self.insetImage, (left, self.rect.top))
            surface.blit(element.iconImage, (left, self.rect.top))